<?php
// +----------------------------------------------------------------------
// | Author: weifashi <605403358@qq.com>
// +----------------------------------------------------------------------
namespace app\admin\actions\controls;
use think\facade\Db;
use app\admin\actions\Methods;

error_reporting(0);

class FormService {
    
    //基础定义
    private $userInfo;//用户信息
    private $param;//用户信息
    private $formNode;//节点
    private $allFieldAttr = array(); // 所有字段属性
    private $gridField; // grid字段
    private $allField; // 所有字段
    private $erreoInfo; // 状态信息,
    private $stateInfo = array(
        '-1' => "找不到form节点！",
        '-2' => "创建视图失败，请检测sql语句是否正确！",
        '-3' => "查询错误，sql只能配置查询动作！",
        '-4' => "请配置表主键！",
        '-5' => "请配置xml的entity属性！",
        '-6' => "请传入oid参数！",
        '-7' => "请传入mode参数！",
    );
    
        
    /**
    * 构造函数自动执行
    * @return null
    */
    public function __construct($xml='',$id=''){
        //错误级别
		error_reporting(E_ERROR | E_PARSE);
        // 
        $this->userInfo = request()->userInfo;
        $this->param = $param = request()->param();
        $nodeId = $id ?: $param['id'] ?: '';
        $xmlFile = Methods::getXmlFile($xml);
        if(!$this->formNode = self::isExistNode($xmlFile,$nodeId)){
            throw new \think\Exception('Form-找不到xml对应节点，或xml文件格式错误！');
        }
    }   

     /**
     * 获取示例
     * @param array $options 实例配置
     * @return static
     */
    public static function instance($p1=null,$p2=null,$p3=null)
    {
        return new self($p1,$p2,$p3);
    }
    
    //验证
    private function validation($sql,$entity,$key,$oid,$mode){
        $flag = true;
        if( !self::isExistDangerous($sql) ){
            $this->erreoInfo =  $this -> stateInfo[-3];
            $flag = false; 
        }else if(!$key || $key==''){
            $this->erreoInfo =  $this -> stateInfo[-4];
            $flag = false; 
        }else if(!$entity || $entity==''){
            $this->erreoInfo =  $this -> stateInfo[-5];
            $flag = false; 
        }else if(!$oid && $oid!=''){
            $this->erreoInfo =  $this -> stateInfo[-6];
            $flag = false; 
        }else if(!$mode && $oid!=''){
            $this->erreoInfo =  $this -> stateInfo[-7];
            $flag = false; 
        }
        return $flag;
    }
    
    /**
    * 获取form选项配置
    * @access public
    * @param string $id             
    * @param string $xml        
    * @return array
    */
    public function getOption(){
        $data = array();
        $oid = Methods::getParam('oid');
        $mode = Methods::getParam('mode') ?: 1;
        $emptyFields = array();
        $formNode = $this->formNode;
        $sql = $formNode -> sql;
        $interface = $formNode ->sql-> interface['url'].'';
        
        $key = $sql->attributes()['key'];
        $entity= $sql->attributes()['entity'];
        
        //验证必填参数
        if( !self::validation($sql,$entity,$key,$oid,$mode) ){
            throw new \think\Exception($this->erreoInfo);
            return false;
        }
        
        //获取字段信息
        $fieldInfo = trim($sql.'') ? self::getFieldInfo($sql) : [];
        //获取转化xml节点数据
        if($interface){
            $dbData = Methods::callApi($interface); 
            if($mode==1 && $dbData){  $mode = 2; }
            $data = self::getXmlData($formNode, $sql, [$dbData], $fieldInfo,$mode );
        }else{
            $dbData = self::getSqlValue($sql,$key,$oid,$mode); 
            $data = self::getXmlData($formNode, $sql, $dbData, $fieldInfo, $mode);
        }
        
        // 追加字段
        $data = self::addField($data,$dbData);
        //转数据
        $data = self::replaceJoinExp($data);
        //验证是否存在空值和重复字段
        if($formNode ->sql['notValidSql'].'' !== 'true' && trim($sql.'') ){
            $emptyFields = self::getEmptyFields($data,$fieldInfo);
            $uniqueFields = self::getUniqueFields($data);
            if($emptyFields){
                throw new \think\Exception("找不到 {$emptyFields} 字段！");
                return false;
            }
            if($uniqueFields){
                throw new \think\Exception("出现重复字段 {$uniqueFields} ！");
                return false;
            }
        }
        
        
        //返回错误或者配置数据
        return $data;
    }
    
    /**
    * 保存数据
    * /admin/Api/Grid/getOption
    * @access public
    * @param obj $param             
    * @param xml $formNode      
    * @return array
    */
    public function saveData(){
        $param = json_decode( $this->param["data"],true);
        $formNode = $this->formNode;
        $oid = Methods::getParam('oid');
        $mode = Methods::getParam('mode') ?: 1;
        $sql = $formNode -> sql;
        $key = $sql->attributes()['key'];
        $entity= $sql->attributes()['entity'];
        $where = Methods::replaceExp( $sql['where'] ?: '' );
        
        //验证必填参数
        if( !self::validation(true,$entity,$key,$oid,$mode) ){
            throw new \think\Exception($this->erreoInfo);
            return false;
        }
        
        //保存
        $dataKey = [];
        $saveData =  $this->getSaveField($formNode,$param['data'],$entity,$key);
        foreach($saveData as $dk => $ds){
            $entityName = $ds['entity'];
            $keyName = $ds['key'];
            $db = Db::table($entityName);
            if( $mode >1 && $where ){
                $res = (clone $db)->where([["$keyName",'=',"$oid"]])->where($where.'')->find();
                if(!$res)  throw new \think\Exception('无权限！');
            }
            // 保存form
            if($ds['type'] != 'grid'){
                foreach($ds['data'] as  $field => $val){
                    $title = $this->allFieldAttr["$field"]['title'].'';
                    $unique = $this->allFieldAttr["$field"]['unique'].'';
                    $errormsg = $this->allFieldAttr["$field"]['errormsg'].'';
                    $ignore = $this->allFieldAttr["$field"]['ignore'].'';
                    //转码
                    $ds['data']["$field"]= htmlspecialchars($val);
                    //验证唯一
                    if($ignore !="true" || ($ignore == "true" && !empty($val) ) ){
                        if( strstr($unique,"/") && !Methods::callApi($unique) ){
                            throw new \think\Exception($errormsg ? "$errormsg" : "【 $title 】 不能添加重复值！");
                            return false;   
                        }else if( $unique =='true' && !strstr($unique,"/") ){
                            $map = []; $map2 = [];
                            $map["$field"] = $val;
                            if( $mode != "1" ){
                                $map2[] = array("$keyName",'<>',"$oid");
                            }
                            if( (clone $db)->where($map) ->where($map2) -> find() ){
                                throw new \think\Exception($errormsg ? "$errormsg" : "【 $title 】 不能添加重复值！");
                                return false;   
                            }
                        }
                    }
                }
                //子表主键赋值
                if($dk>0 && count($dataKey) ){
                    $ds['data']["{$ds['key']}"] = $dataKey[0];
                }
                //保存数据
                if(!$this->erreoInfo){
                    if($formNode['saveUrl']){
                        $ds['data'][$keyName] = "$oid";
                        $res = Methods::callApi($formNode['saveUrl'].'',$ds['data']);
                        if($res->getData()['code'] != 0){
                            throw new \think\Exception( $res->getData()['msg'] ?: '异常错误');
                        }
                    }else{
                        if($mode == 1){
                            $dataKey[] = (clone $db)->insertGetId($ds['data']);
                        }else{
                            (clone $db)->where([["$keyName",'=',"$oid"]])->update($ds['data']);
                            $dataKey[] = intval("$oid");
                        }
                    }
                }
            }
            
            //保存grid  -  自动识别，删除，更新 ，新增
            if( $ds['type'] == 'grid' && ($gridData = $param['data']['__'.$ds['id'].'__']) ){
                $queryField = $ds['queryField'];
                $ids = [];  $saData = [];
                foreach( $gridData as $key => $row ){
                    $row[$queryField] = $dataKey[0];
                    if( !$row[$keyName] ){
                        $saData[] = $row;
                    }else{
                        $ids[] = $row[$keyName];
                        (clone $db)->where($keyName,$row[$keyName])->strict(false)->update($row);
                    }
                }
                (clone $db)->where($queryField,$dataKey[0])->where($keyName,'not in',$ids)->delete();
                (clone $db)->strict(false)->insertAll($saData);
            }
            
        }
        
        return [ 'key'=>$dataKey[0] ];
    }
    
    /**
    * 获取xml数据
    * /admin/Api/Grid/getOption
    * @access private
    * @param obj $nodeData              
    * @param xml $sqlNode       
    * @param array $dbData      
    * @param array $fieldInfo       
    * @param string $mode       
    * @return array
    */
    private function getXmlData($nodes,$sqlNode,$dbData,$fieldInfo,$mode){
        $data = self::getXmlNodeAttrs($nodes);
        $type = $data['type'];
        $dataUrls = ['select','multiSelect','radio','checkbox'];
        
        //查找子节点
        foreach($nodes as $key => $ds ){
            if( in_array($key,['sql','saves','save']) ) continue;
            
            $attr = self::getXmlNodeAttrs($ds);
            if( $key=='option' )  $attr['label'] =  $attr['label'] ?? $ds.'';
            
            foreach($ds as $k => $d ){
                
                //行属性
                if($k=='row'){
                    $dattr = self::getXmlNodeAttrs($d);
                    $field = $dattr['field'];
                    foreach( $dattr as $ks => $ar ){
                        if( $field && $ks == 'value' && $mode != 1 ){
                            $edit_val = $d["editModeValue"] ?? $d["editValue"] ?? '';
                            $rxp_val = $edit_val ? $edit_val : self::getValue($dbData,$field,$dattr,$sqlNode);
                        }else{
                            $rxp_val = self::replaceExp($ar,$d,$sqlNode,$dbData);
                        }
                        if( $field && $ks == 'value' ){
                            $d["value"] =  $rxp_val;
                            if( !is_array(json_decode($rxp_val,true)) ){
                                $d["join_field"] = $field.'=>'.$rxp_val;
                            }
                            $d["maxLength"] = self::getMaxLength($fieldInfo,$field);
                        }
                        if($d["edit"] == '1' && $mode!=1) $d["edit"] = "false";
                        if($d["edit"] == '2' && $mode!=2) $d["edit"] = "false";
                        if($d["edit"] == '3' && $mode!=3) $d["edit"] = "false";
                        if($field && $mode==3 ) $d["edit"] = "false";
                        if($ks != 'edit') $d[$ks] = $rxp_val; 
                    }
                }

                //递归循环
                $attr[$k][] = self::getXmlData($d,$sqlNode,$dbData,$fieldInfo,$mode);
            }
            
            //收集
            in_array($key,['section','option'])  ? $data[$key][] = $attr : $data[$key] = $attr;
        }

        //赋值下拉选项值
        if( in_array( $data['type'], $dataUrls) ){
            $data['option'] = $data['option'] ?? [];
            if(!empty($data['dataUrl'])){
                $selectdata = Methods::callApi( $data['dataUrl'] );
                if( !is_array($selectdata) ){ $selectdata = $selectdata->getData()['data']; }
                foreach($selectdata as  $h ){
                    $h['label'] = $h['label'] ?? $h['name'] ?? '';
                    $h['value'] = $h['value'] ?? $h['id'] ?? '';
                    $data['option'][] = $h;
                }
            }
        }
        
        //字段收集
        if( $data['type']== 'grid' ) $this->gridField[] = $data['queryField'];
        if($data['field'])   $this->allField[] = $data['field'];
        
        return $data;
    }
    
    /**
    * 获取xml节点属性 建值对
    *  @return  array
    */
    private function getXmlNodeAttrs($node){
        $data = [];
        if($node){
            foreach($node->attributes() as $key => $attr ){
                $data["$key"]="$attr";
            }
        }
        return $data;
    }
    
     /**
    * 追加字段
    * @access private
    * @param array $data        
    * @return array
    */
    private function addField($data,$dbData){
        if($this->gridField){
            foreach($this->gridField as $k=>$field){
                if( !in_array($field,$this->allField) ){
                    $data['tabs']['tab'][0]['section'][0]['row'][] = [
                        "type"=>"hidden",
                        "field"=>$field,
                        "value"=>$dbData[0][$field] ?? $dbData[0]['id'] ?? '',
                    ];
                }
            }
        }
        return $data;
    }
    
    /**
    * 替换Join占位符
    * @access private
    * @param array $data        
    * @return array
    */
    private function replaceJoinExp($data){
        $dataStr = json_encode($data,JSON_UNESCAPED_UNICODE);
        preg_match_all( '/(?<="join_field":")[^"]+/',$dataStr,$match);
        preg_match_all( '/(?<={{join:)[^}}]+/',$dataStr,$match2);
        foreach($match[0] as  $ds ){
            $dss = explode("=>", $ds);
            foreach($match2[0] as  $join ){
                if( $join == $dss[0] ){
                    $dataStr = preg_replace("/{{join:{$join}}}/",$dss[1],$dataStr);
                }
            }
        }
        $dataStr = preg_replace("/{{.*?}}/",'',$dataStr);
        $dataStr = preg_replace('/,"join_field":".*?",/',',',$dataStr);
        return json_decode($dataStr,true);
    }
    
    /**
    * 替换占位符
    * @access private
    * @param array $data        
    * @return array
    */
    private function replaceExp($src,$attr,$sqlNode,$dbData){
        $src = Methods::replaceExp($src,$attr);
        preg_match_all( '/(?<={{)[^}}]+/',$src,$match);
        foreach( $match[0] as $val ){
            $v = "{{{$val}}}";
            switch ($val){
                case strstr($val,"++") ? true : false: //自增
                    $entity = $sqlNode['entity'];
                    $digit = str_replace("base:","",$val);
                    $digit = str_replace("++","",$digit);
                    $d = Db::table("$entity") -> field("max({$attr['field']} + 0) as max") ->select();
                    $v = $d[0]["max"] ? $d[0]["max"] + 1 : 1;
                    if($digit){ $v = sprintf("%0{$digit}d", $v); }
                    $val = str_replace("++","kk",$val);
                    $src = str_replace("++","kk",$src);
                    break;
                case strstr($val,"field:") ? true : false: //字段
                    $field = str_replace("field:","",$val);
                    $v =  self::getValue($dbData,$field,$attr,$sqlNode);
                    break;
            }
            $src = preg_replace("{{{".$val."}}}", $v ,$src);
        }
        return  $src;
    }
    
    /**
    * 获取空字段
    * @access private
    */
    private function getEmptyFields($data,$formFields){
        $arr = [];
        preg_match_all( '/(?<="field":")[^"]+/',json_encode($data),$match);
        preg_match_all( '/(?<="column_name":")[^"]+/',json_encode($formFields),$match2);
        foreach($match[0] as  $field ){
            if(!in_array($field, $match2[0]) && $field !=""){ 
                $arr[]= $field;
            }
        }
        return implode(", ",$arr);
    }
    
    /**
    * 获取重复字段
    * @access private
    */
    private function getUniqueFields($data){
        $arr = [];
        preg_match_all( '/(?<="field":")[^"]+/',json_encode($data),$match);
        $unique_arr = array_unique($match[0]); 
        $repeat_arr = array_diff_assoc($match[0], $unique_arr); 
        return  implode(",",array_unique($repeat_arr));
    }
    
    /**
    * 获取字段属性
    * @access private
    */
    private function getFieldAttr($formNode,$field){
        $attr = [];
        foreach($formNode -> tabs ->tab as $tab){
            foreach($tab-> section as $section ){
                foreach($section->row  as $row ){
                    if($row -> attributes()["field"] == $field){
                        $attr  = $row-> attributes();
                    }
                }
            }
        }
        return $attr;
    }
    
    /**
    * 获取保存字段
    * @access private
    */
    private function getSaveField($formNode,$saveData,$entity,$key){
        $key = strstr( $key.'' ,".") ? strtr( strstr( $key.'' ,".") ,"."," ") : $key.'';
        $notSave = explode(",", $formNode -> sql->attributes()['notSave'] );
        if( in_array('*',$notSave) ){
            throw new \think\Exception('无权限！');
        }
        
        $data = array();
        
        //获取所有已配置的可收集字段
        $allFields = [];
        $gridList = [];
        foreach($formNode -> tabs ->tab as $tab){
            foreach($tab-> section as $section ){
                foreach($section->row  as $row ){
                    $this->allFieldAttr["{$row['field']}"] = $row;
                    if($row -> attributes()["needSave"] != 'false' && $row['field'].''){
                        array_push($allFields, $row['field'].'' );
                    }else{
                        array_push($notSave, $row['field']);
                    }
                    //grid
                    if($row['type'] == 'grid' && $row['entity'] ){
                        $gridList[] = [
                            'entity'=>$row['entity'].'',
                            'key'=>$row['key'].'' ?: 'id',
                            'queryField'=>$row['queryField'].'',
                            'id'=>$row['id'].'' ?? '',
                            'type'=>$row['type'].'',
                            'data'=>[],
                        ];
                    }
                }
            }
        }
        
        // 保存标签处理
        foreach( $formNode->saves->save ?: [] as $d){
            $fields = explode(",", $d['field']);
            $allFields = array_merge( $allFields, $fields );
            array_push($data,array("entity"=>$d["entity"].'',"key"=>$d["key"].'','field'=>$fields ));
        }
        
        //获取可保存数据
        $preservable = [];
        foreach($saveData as $k => $d ){
            if( in_array($k, $allFields) ){
                if( $this->getFieldAttr($formNode,$k)['saveFormat'] == 'md5' ){
                    $preservable[$k] =  md5($d);
                }else{
                    $preservable[$k] = $d;
                }
            }
        }
        
        //装载到配置表中
        $keyData = [];
        foreach($preservable as $k => $v ){
            foreach($data as $ke=>$ds ){
                if( in_array($k, $ds['field']) || $k == $ds['key'] ){
                    $data[$ke]['data'][$k] = $v;
                }
            }
            if( (!in_array($k,$notSave) || $k == $key) && !in_array('*',$notSave) ){
                $keyData[$k] = $v;
            }
        }
        array_unshift($data, array("entity"=>$entity.'',"key"=>$key,'field'=>'',"data"=>$keyData));
        
        //保存grid列表
        foreach($gridList as $ke=>$ds ){ array_push($data,$ds);  }
        
        // 返回
        return $data;
    }
    
    /**
    * 获取值
    * @access private
    * @param string $field 行字段名称
    * @param array  $data xml sq语句查询出的数据
    * @param array $rows 索引行
    * @return string
    */
    private function getValue($dbData,$field,$attr,$sqlNode){
        $val = "";
        $type= $attr["type"];
        $format= $attr["format"];
        $editValue= $attr["editValue"] ?: $attr["editModeValue"];
        $isField=false;
        foreach($dbData as  $vs ){
            foreach($vs as $key => $v){
                if($field == $key){
                    $isField = true;
                    switch ($type){
                        case "datetime":
                            if($v && $v != "0000-00-00 00:00:00" && $format !='HH:mm' && $attr['range'] != 'true'){
                                $format= Methods::getFormat($format);
                                $val    =  date_format(date_create("$v"),"$format");
                            }else{
                                $val = !$v ? "" : $v;
                            }
                            break;
                        case "number":
                            $val = !$v ? 0 : $v;
                            break;
                        default:
                            $val = $v == null ? '' : $v;          
                    }
                }
            }
        };
        if($editValue){ $val = self::replaceExp($editValue,$attr,$sqlNode,$data);}
        return ($isField ? $val : "");
    }
    
    /**
    * 获取长度
    * @access private
    * @param string $information  xml-sql中查询出的字段信息
    * @param string $field            指定的字段
    * @return string
    */
    private function getMaxLength($fieldInfo,$field){
        $maxLength = "";
        if($field){
            foreach($fieldInfo as $key => $arrs ){
                if($arrs['column_name'] == $field){
                    $maxLength= $arrs['character_maximum_length'];
                }
            };
        }
        return $maxLength  ?  $maxLength : '';
    }
    
    
    /**
    * 监听xmlsql语句是否有除了查询以外的动作
    * @access private
    * @return string
    */
    private function isExistDangerous($sql){
        $sql = strtolower($sql);
        $sql = preg_replace('/\binsert\s|\bupdate\s|\bdelete\s|\bcreate\s|\balter\s|\bdrop\s|(\bexec|\bexecute)[\s\(]|\bsp_|\bxp_|\sinto\s/','error--error',$sql);
        if(strstr($sql,"error--error")){
            return false;
        }else{
            return $sql;
        }
    }
    
    /**
    * 获取数据库值
    * @access private
    * @param string $id 
    * @param string $xml 
    * @param string $mode
    * @param string $oid
    * @return  string
    */
    private function getSqlValue($sql,$key,$oid,$mode){
        $param = self::getUrlParam();
        $mode = $mode ? $mode : $param["mode"]; 
        $oid = $mode == 1 ? 0 : $oid; 
        if(!$oid) return [];
        $sqls = str_replace("1=1",explode(",",$key)[0]."='".$oid."'",$sql) ;
        if($where = Methods::replaceExp( $sql['where'] ?: '' )){
            $sqls = $sqls.'and '.$where;
        }
        $data = self::multiQuery($sqls,$sql['showSql'].'');
        if( !$sql['showSql'].'' && !$data){
            throw new \think\Exception('无权限！');
        }
        return $data;
    }
    
    private function getUrlParam(){
        $http = parse_url($_SERVER['HTTP_REFERER']);
        $query = $http["query"];
        parse_str($query,$parr);
        return $parr;
    }
    
    /**
    * 获取sql字段信息
    * @access private
    * @param string $id         
    * @param string $xml    
    * @return string
    */
    private function getFieldInfo($sql){
        //1.先创建视图 ,2.再获取视图中字段属性 ,3.然后删除视图
        $sqls = "
            create or replace view xw_form_view  as ($sql);

            select column_name,column_comment,data_type,character_maximum_length
            from information_schema.columns 
            where table_name='xw_form_view';

            drop view  xw_form_view;
        ";
        return self::multiQuery($sqls); 
    }
    
    //多查询
    private function multiQuery($sqls="",$showSql=''){
        //替换店铺id    
        $shop_id = $this->userInfo['shop_id'] ?: 0;
        $sqls = str_replace("{{base:当前店铺}}", $shop_id ? "shop_id=$shop_id" : '10=10',$sqls);    
        $sqls = str_replace("{{base:shop_id}}", $shop_id,$sqls);    
        //是否显示sql
        if($showSql=='true') throw new \think\Exception($sqls);
        //查询数据
        $data=array();
        $c = config('database.connections.mysql');
        $conn = new \mysqli( $c['hostname'], $c['username'],$c['password'],$c['database'],$c['hostport']);
        if (mysqli_connect_errno()){
            $this->erreoInfo = "连接到 MySQL 失败: " . mysqli_connect_error();
            throw new \think\Exception($this->erreoInfo);
        }else{
            $conn->query("set names utf8");
            if(mysqli_multi_query($conn, $sqls)) {
                do {
                    if($result = mysqli_store_result($conn)) {
                        while( $row = mysqli_fetch_assoc($result)  ) {
                            array_push($data, $row);
                        }
                        mysqli_free_result($result);        
                    }
                } while(mysqli_next_result($conn));
            }
            if($conn->error){
                $this->erreoInfo = 'sqlError:  ' . $conn->error;
                throw new \think\Exception($this->erreoInfo);
                mysqli_close($conn);
            }
            mysqli_close($conn);
        }
        return $data;
    }
    
    
    /**
    * 是否存在grid节点
    * @param string $xmlFile            xml-id
    * @return  bu
    */
    private function isExistNode( $xmlFile,$id ){
        $node = false;
        foreach( $xmlFile->form as $val){
            if( $id == $val->attributes()["id"] ){
                $node = $val;
            }
        }
        return $node;
    }
    
    
}




